package com.google.qam.view;

import java.util.Arrays;

import com.google.qam.R;
import com.google.qam.app.App;
import com.google.qam.texture.DrawableTexture;
import com.google.qam.texture.ResourceTexture;

import android.content.Context;
import android.graphics.Rect;
import android.graphics.drawable.BitmapDrawable;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.View.OnTouchListener;
import android.view.ViewGroup.LayoutParams;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.PopupWindow;
import android.widget.ScrollView;
import android.widget.TextView;

public abstract class PopupMenu {
	public static final int BOX = 0;
	public static final int SLIDER = 1;
	
	public static final int ANIM_GROW_FROM_LEFT = 0;
	public static final int ANIM_GROW_FROM_RIGHT = 1;
	public static final int ANIM_GROW_FROM_CENTER = 2;
	public static final int ANIM_REFLECT = 3;
	public static final int ANIM_AUTO = 4;

	final Context mContext;
	final PopupWindow mPopupWindow;
	final LayoutInflater mInflater;
	final WindowManager mWindowManager;
	final int mAnim;
	
	Listener mListener;
	Option[] mOptions = {};
	boolean mNeedsLayout;
	int mSelectedItem = -1;
	
	public PopupMenu(Context context, int anim) {
		mContext = context;
		mAnim = anim;
		
		mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
		mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
		
		mPopupWindow = new PopupWindow(context);
		initialize();
	}
	
	public abstract void setOptions(Option[] options);
	public abstract void setListener(Listener listener);
	public abstract void showAtPoint(View parent, int x, int y, int width, int height);
	
	public void close() {
		mPopupWindow.dismiss();
	}
	
	public void showAtPoint(View parent) {
		int[] location = new int[2];
		parent.getLocationOnScreen(location);
		showAtPoint(parent, location[0], location[1], parent.getWidth(), parent.getHeight());
	}
	
	private void initialize() {
		mPopupWindow.setBackgroundDrawable(new BitmapDrawable());
		mPopupWindow.setWidth(WindowManager.LayoutParams.WRAP_CONTENT);
		mPopupWindow.setHeight(WindowManager.LayoutParams.WRAP_CONTENT);
		mPopupWindow.setOutsideTouchable(true);
		mPopupWindow.setTouchable(true);
	}
	
	protected void setAnimationStyle(int arrowWidth, int screenWidth, int requestedX, boolean onTop) {
		int arrowPos = requestedX - arrowWidth / 2;
		
		switch(mAnim)
		{
			case ANIM_GROW_FROM_LEFT:
				mPopupWindow.setAnimationStyle((onTop) ? R.style.Animations_PopUpMenu_Left : R.style.Animations_PopDownMenu_Left);
				break;
				
			case ANIM_GROW_FROM_RIGHT:
				mPopupWindow.setAnimationStyle((onTop) ? R.style.Animations_PopUpMenu_Right : R.style.Animations_PopDownMenu_Right);
				break;
				
			case ANIM_GROW_FROM_CENTER:
				mPopupWindow.setAnimationStyle((onTop) ? R.style.Animations_PopUpMenu_Center : R.style.Animations_PopDownMenu_Center);
				break;
				
			case ANIM_REFLECT:
				mPopupWindow.setAnimationStyle((onTop) ? R.style.Animations_PopUpMenu_Reflect : R.style.Animations_PopDownMenu_Reflect);
				break;
				
			case ANIM_AUTO:
				if (arrowPos <= screenWidth / 4) {
					mPopupWindow.setAnimationStyle((onTop) ? R.style.Animations_PopUpMenu_Left : R.style.Animations_PopDownMenu_Left);
				} else if(arrowPos > screenWidth / 4 && arrowPos < 3 * (screenWidth / 4)) {
					mPopupWindow.setAnimationStyle((onTop) ? R.style.Animations_PopUpMenu_Center : R.style.Animations_PopDownMenu_Center);
				} else {
					mPopupWindow.setAnimationStyle((onTop) ? R.style.Animations_PopUpMenu_Right : R.style.Animations_PopDownMenu_Right);
				}
				break;
		}
	}
	
	protected void showArrow(View showArrow, View hideArrow, int requestedX) {
        final int arrowWidth = showArrow.getMeasuredWidth();
        showArrow.setVisibility(View.VISIBLE);
        ViewGroup.MarginLayoutParams param = (ViewGroup.MarginLayoutParams)showArrow.getLayoutParams();
        param.leftMargin = requestedX - arrowWidth / 2;
        hideArrow.setVisibility(View.INVISIBLE);
    }
	
	protected void setSelectedItem(int hit) {
		if(mSelectedItem != hit) {
			setDrawable(hit);
			mSelectedItem = hit;
			if (mListener != null) {
				mListener.onSelectionChanged(this, hit);
			}
		}
	}
	
	protected abstract void layout();
	protected abstract void setDrawable(int hit);
	protected abstract int hitTestOptions(View view, int x, int y);
	
	public interface Listener {
        void onSelectionClicked(PopupMenu menu, int hit);
        void onSelectionChanged(PopupMenu menu, int hit);
    }
	
	public static final class Builder {
		private final Context mContext;
		private int mType;
		private int mAnim;
		
		public Builder(Context context) {
			mContext = context;
		}
		
		public Builder type(int type) {
			mType = type;
			return this;
		}
		
		public Builder animation(int anim) {
			mAnim = anim;
			return this;
		}
		
		public PopupMenu build() {
			final int type = (mType < PopupMenu.BOX || mType > PopupMenu.SLIDER) ? PopupMenu.BOX : mType;
			final int anim = (mAnim < PopupMenu.ANIM_GROW_FROM_LEFT || mAnim > PopupMenu.ANIM_AUTO) ? PopupMenu.ANIM_AUTO : mAnim;
			return (type == PopupMenu.BOX) ? new PopupMenuBox(mContext, anim) : new PopupMenuSlider(mContext, anim);
		}
	}
	
	public static final class Option {
		public final int mIcon;
		public final String mTitle;
		public final Runnable mAction;
		
		public Option(String title, int icon, Runnable action) {
            mTitle = title;
            mIcon = icon;
            mAction = action;
        }

		@Override
		public int hashCode() {
			final int prime = 31;
			int result = 1;
			result = prime * result + mIcon;
			result = prime * result + ((mTitle == null) ? 0 : mTitle.hashCode());
			return result;
		}

		@Override
		public boolean equals(Object obj) {
			if (this == obj)
				return true;
			if (obj == null)
				return false;
			if (getClass() != obj.getClass())
				return false;
			Option other = (Option) obj;
			if (mIcon != other.mIcon)
				return false;
			if (mTitle == null) {
				if (other.mTitle != null)
					return false;
			} else if (!mTitle.equals(other.mTitle))
				return false;
			return true;
		}
	}
	
	private final static class PopupMenuBox extends PopupMenu {
		private static final int LAYOUT_ITEM_BACKGROUND_NORMAL = 0x00000000;
		private static final int LAYOUT_ITEM_BACKGROUND_PRESSED = R.drawable.popup_box_item_pressed;
		private static final int LAYOUT_ITEM_PADDING_LEFT = (int)(6 * App.PIXEL_DENSITY);
	    private static final int LAYOUT_ITEM_PADDING_TOP = 	(int)(8 * App.PIXEL_DENSITY);
	    private static final int LAYOUT_ITEM_PADDING_RIGHT = (int)(6 * App.PIXEL_DENSITY);
	    private static final int LAYOUT_ITEM_PADDING_BOTTOM = (int)(8 * App.PIXEL_DENSITY);
	    private static final boolean LAYOUT_ITEM_CLICKABLE = true;
	    private static final boolean LAYOUT_ITEM_FOCUSABLE = true;
	    
	    private static final int LAYOUT_TITLE_PADDING_LEFT = (int)(10 * App.PIXEL_DENSITY);
		private static final int LAYOUT_TITLE_PADDING_TOP = (int)(0 * App.PIXEL_DENSITY);
		private static final int LAYOUT_TITLE_PADDING_RIGHT = (int)(5 * App.PIXEL_DENSITY);
		private static final int LAYOUT_TITLE_PADDING_BOTTOM = (int)(0 * App.PIXEL_DENSITY);
		private static final int LAYOUT_ITEM_ORIENTATION = LinearLayout.HORIZONTAL;
		private static final int LAYOUT_TITLE_TEXT_COLOR = 0xffffffff;
		private static final int LAYOUT_TITLE_GRAVITY = Gravity.CENTER_VERTICAL;
		private static final float LAYOUT_TITLE_TEXT_SIZE = 12f * App.PIXEL_DENSITY;
		
		private static final LayoutParams LAYOUT_ITEM;
		private static final LayoutParams LAYOUT_TITLE;
		static {
			LAYOUT_ITEM = new LinearLayout.LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT);
			LAYOUT_TITLE = new LinearLayout.LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);
		}
		
		private final View mRoot;
		private final ImageView mArrowUp;
		private final ImageView mArrowDown;
		private final ViewGroup mTrack;
		private final ScrollView mScroller;
		
		public PopupMenuBox(Context context, int anim) {
			super(context, anim);
			
			mRoot = mInflater.inflate(R.layout.popup_box, null);
			mArrowDown = (ImageView) mRoot.findViewById(R.id.box_arrow_down);
			mArrowUp = (ImageView) mRoot.findViewById(R.id.box_arrow_up);
			mTrack = (ViewGroup) mRoot.findViewById(R.id.box_tracks);
			mScroller = (ScrollView) mRoot.findViewById(R.id.box_scroller);
			
			mPopupWindow.setContentView(mRoot);
		}
		
		@Override
		public void setListener(Listener listener) {
			mListener = listener;
			
			// When a touch even happens outside of the window
			// make the popup window go away
			mPopupWindow.setTouchInterceptor(new OnTouchListener() {
			public boolean onTouch(View v, MotionEvent event) {
				int hit = hitTestOptions(v, (int)event.getX(), (int)event.getY());
				switch(event.getAction())
				{
					case MotionEvent.ACTION_DOWN:
					case MotionEvent.ACTION_MOVE:
						setSelectedItem(hit);
						break;
							
					case MotionEvent.ACTION_UP:
						if (hit != -1 && mSelectedItem == hit)
						{
							mOptions[hit].mAction.run();
							if (mListener != null)
								mListener.onSelectionClicked(PopupMenuBox.this, hit);
								
							// Set normal background after click
							View normal = mTrack.getChildAt(hit);
							final DrawableTexture d = DrawableTexture.createDrawableTexture(String.valueOf(LAYOUT_ITEM_BACKGROUND_NORMAL));
							normal.setBackgroundDrawable(d.isLoaded()
									? d.loadColor()
									: d.setPadding(
											normal.getPaddingLeft(),
											normal.getPaddingTop(),
											normal.getPaddingRight(),
											normal.getPaddingBottom())
										.loadColor());
								
							// Reset the selected item
							mSelectedItem = -1;
						}
						break;
							
					case MotionEvent.ACTION_CANCEL:
						setSelectedItem(-1);
						break;
				}
				return true;
			}});
		}

		@Override
		public void setOptions(Option[] options) {
			if(mOptions.length == 0) {
				mOptions = options;
				mNeedsLayout = true;
			} else if(mOptions.length > 0 && !Arrays.equals(mOptions, options)) {
				// Remove old items
				mTrack.removeAllViews();
				mOptions = options;
				mNeedsLayout = true;
			}
		}

		@Override
		public void showAtPoint(View parent, int x, int y, int width, int height) {
			if(mNeedsLayout)
				layout();
			
			int xPos, yPos;
			Rect parentRect = new Rect(x, y, x + width, y + height);
			
			mRoot.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
			mRoot.measure(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
			
			int rootHeight = mRoot.getMeasuredHeight();
			int rootWidth =  mRoot.getMeasuredWidth();
			int screenHeight = mWindowManager.getDefaultDisplay().getHeight();
			int screenWidth = mWindowManager.getDefaultDisplay().getWidth();
			
			// Automatically get X coordinate of menu (top left)
			if((parentRect.left + rootWidth) > screenWidth) {
				xPos = parentRect.left - (rootWidth - width);
			} else {
				if (width > rootWidth) {
					xPos = parentRect.centerX() - (rootWidth/2);
				} else {
					xPos = parentRect.left;
				}
			}
			
			int dyTop = parentRect.top;
			int dyBottom = screenHeight - parentRect.bottom;
			boolean onTop = (dyTop > dyBottom) ? true : false;
			
			if(onTop) {
				if(rootHeight > dyTop) {
					yPos = 15;
					LayoutParams l = mScroller.getLayoutParams();
					l.height = dyTop - height;
				} else {
					yPos = parentRect.top - rootHeight;
				}
			} else {
				yPos = parentRect.bottom;
				if(rootHeight > dyBottom) {
					LayoutParams l = mScroller.getLayoutParams();
					l.height = dyBottom;
				}
			}
			
			showArrow(onTop ? mArrowDown : mArrowUp, onTop ? mArrowUp : mArrowDown, parentRect.centerX() - xPos);
			setAnimationStyle(mArrowUp.getMeasuredWidth(), screenWidth, parentRect.centerX(), onTop);
			if(mPopupWindow.isShowing()) mPopupWindow.dismiss();
			mPopupWindow.showAtLocation(parent, Gravity.NO_GRAVITY, xPos, yPos);
		}

		@Override
		protected int hitTestOptions(View view, int x, int y) {
			Option[] options = mOptions;
			int numOptions = options.length;
			
			int mWidth = view.getMeasuredWidth();
			if(numOptions != 0 && x >= 0 && x < mWidth && y >= 0) {
				int padding = LAYOUT_ITEM_PADDING_TOP + LAYOUT_ITEM_PADDING_BOTTOM;
				for (int i = 0; i != numOptions; i++) {
					if(y < mTrack.getChildAt(i).getBottom() + padding) {
						return i;
					}
				}
			}
			return -1;
		}

		@Override
		protected void layout() {
			// Mark as not needing layout.
			mNeedsLayout = false;
			
			Option[] options = mOptions;
			int numOptions = options.length;
			for (int i = 0; i != numOptions; ++i) {
				final LinearLayout item = (LinearLayout) mInflater.inflate(R.layout.popup_item, null);
				final ImageView itemIcon = (ImageView) item.findViewById(R.id.icon);
				final TextView itemTitle = (TextView) item.findViewById(R.id.title);
				
				// Set custom item settings
				item.setLayoutParams(LAYOUT_ITEM);
				item.setPadding(LAYOUT_ITEM_PADDING_LEFT, LAYOUT_ITEM_PADDING_TOP, 
						LAYOUT_ITEM_PADDING_RIGHT, LAYOUT_ITEM_PADDING_BOTTOM);
				
				final DrawableTexture d = DrawableTexture.createDrawableTexture(String.valueOf(LAYOUT_ITEM_BACKGROUND_NORMAL));
				item.setBackgroundDrawable(d.isLoaded()
						? d.loadColor()
						: d.setPadding(
								item.getPaddingLeft(),
								item.getPaddingTop(),
								item.getPaddingRight(),
								item.getPaddingBottom())
							.loadColor());
				
				item.setOrientation(LAYOUT_ITEM_ORIENTATION);
				item.setClickable(LAYOUT_ITEM_CLICKABLE);
				item.setFocusable(LAYOUT_ITEM_FOCUSABLE);
				
				// Set custom title settings
				itemTitle.setLayoutParams(LAYOUT_TITLE);
				itemTitle.setGravity(LAYOUT_TITLE_GRAVITY);
				itemTitle.setPadding(LAYOUT_TITLE_PADDING_LEFT, LAYOUT_TITLE_PADDING_TOP,
						LAYOUT_TITLE_PADDING_RIGHT, LAYOUT_TITLE_PADDING_BOTTOM);
				itemTitle.setTextSize(LAYOUT_TITLE_TEXT_SIZE);
				itemTitle.setTextColor(LAYOUT_TITLE_TEXT_COLOR);
				
				final Option option = options[i];
				if(option.mIcon != -1)
					itemIcon.setImageBitmap(ResourceTexture.createResourceTexture(option.mIcon).load(mContext));
				
				if(option.mTitle != null)
					itemTitle.setText(option.mTitle);
				
				mTrack.addView(item);
			}
		}

		@Override
		protected void setDrawable(int hit) {
			if(mSelectedItem == -1 && hit >= 0) {
				View pressed = mTrack.getChildAt(hit);
				final DrawableTexture d = DrawableTexture.createDrawableTexture(String.valueOf(LAYOUT_ITEM_BACKGROUND_PRESSED));
				pressed.setBackgroundDrawable(d.isLoaded()
						? d.loadNinePatch(mContext)
						: d.setPadding(
								pressed.getPaddingLeft(),
								pressed.getPaddingTop(),
								pressed.getPaddingRight(),
								pressed.getPaddingBottom())
							.loadNinePatch(mContext));
				
			} else if(mSelectedItem >= 0 && hit >= 0) {
				View normal = mTrack.getChildAt(mSelectedItem);
				final DrawableTexture dNormal = DrawableTexture.createDrawableTexture(String.valueOf(LAYOUT_ITEM_BACKGROUND_NORMAL));
				normal.setBackgroundDrawable(dNormal.isLoaded()
						? dNormal.loadColor()
						: dNormal.setPadding(
								normal.getPaddingLeft(),
								normal.getPaddingTop(),
								normal.getPaddingRight(),
								normal.getPaddingBottom())
							.loadColor());
				
				View pressed = mTrack.getChildAt(hit);
				final DrawableTexture dPressed = DrawableTexture.createDrawableTexture(String.valueOf(LAYOUT_ITEM_BACKGROUND_PRESSED));
				mTrack.getChildAt(hit).setBackgroundDrawable(dPressed.isLoaded()
						? dPressed.loadNinePatch(mContext)
						: dPressed.setPadding(
								pressed.getPaddingLeft(),
								pressed.getPaddingTop(),
								pressed.getPaddingRight(),
								pressed.getPaddingBottom())
							.loadNinePatch(mContext));
				
			} else if(mSelectedItem >= 0 && hit == -1) {
				View normal = mTrack.getChildAt(mSelectedItem);
				final DrawableTexture d = DrawableTexture.createDrawableTexture(String.valueOf(LAYOUT_ITEM_BACKGROUND_NORMAL));
				normal.setBackgroundDrawable(d.isLoaded()
						? d.loadColor()
						: d.setPadding(
								normal.getPaddingLeft(),
								normal.getPaddingTop(),
								normal.getPaddingRight(),
								normal.getPaddingBottom())
							.loadColor());
			}
		}
	}
	
	private static final class PopupMenuSlider extends PopupMenu {
		private static final int LAYOUT_ITEM_BACKGROUND_NORMAL = R.drawable.popup_slider_item_normal;
		private static final int LAYOUT_ITEM_BACKGROUND_PRESSED = R.drawable.popup_slider_item_pressed;
	    private static final int LAYOUT_ITEM_PADDING_LEFT = (int)(10 * App.PIXEL_DENSITY);
	    private static final int LAYOUT_ITEM_PADDING_TOP = (int)(0 * App.PIXEL_DENSITY);
	    private static final int LAYOUT_ITEM_PADDING_RIGHT = (int)(10 * App.PIXEL_DENSITY);
	    private static final int LAYOUT_ITEM_PADDING_BOTTOM = (int)(0 * App.PIXEL_DENSITY);
	    private static final boolean LAYOUT_ITEM_CLICKABLE = true;
	    private static final boolean LAYOUT_ITEM_FOCUSABLE = true;
	    
	    private static final int LAYOUT_TITLE_PADDING_LEFT = (int)(5 * App.PIXEL_DENSITY);
		private static final int LAYOUT_TITLE_PADDING_TOP = (int)(0 * App.PIXEL_DENSITY);
		private static final int LAYOUT_TITLE_PADDING_RIGHT = (int)(10 * App.PIXEL_DENSITY);
		private static final int LAYOUT_TITLE_PADDING_BOTTOM = (int)(0 * App.PIXEL_DENSITY);
		private static final int LAYOUT_ITEM_ORIENTATION = LinearLayout.HORIZONTAL;
		private static final int LAYOUT_TITLE_TEXT_COLOR = 0xff666666;
	    private static final int LAYOUT_ITEM_GRAVITY = Gravity.CENTER;
	    private static final int LAYOUT_TITLE_GRAVITY = Gravity.CENTER_VERTICAL;
	    private static final float LAYOUT_TITLE_TEXT_SIZE = 12f * App.PIXEL_DENSITY;
		
		private static final LayoutParams LAYOUT_ITEM;
		private static final LayoutParams LAYOUT_TITLE;
		static {
			LAYOUT_ITEM = new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
			LAYOUT_TITLE = new LinearLayout.LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);
		}
		
		private final View mRoot;
		private final ImageView mArrowDown;
		private final ImageView mArrowUp;
		private final ViewGroup mTrack;
		private final Animation mTrackAnimation;
		
		public PopupMenuSlider(Context context, int anim) {
			super(context, anim);
			
			mRoot = mInflater.inflate(R.layout.popup_slider, null);
			mArrowDown = (ImageView) mRoot.findViewById(R.id.slider_arrow_down);
			mArrowUp = (ImageView) mRoot.findViewById(R.id.slider_arrow_up);
			mTrack = (ViewGroup) mRoot.findViewById(R.id.slider_tracks);
			
			mTrackAnimation = AnimationUtils.loadAnimation(context, R.anim.rail);
			mTrackAnimation.setInterpolator(new Interpolator() {
				public float getInterpolation(float t) {
					// Pushes past the target area, then snaps back into place.
					// Equation for graphing: 1.2-((x*1.6)-1.1)^2
					final float inner = (t * 1.55f) - 1.1f;
					return 1.2f - inner * inner;
				}
			});
			
			mPopupWindow.setContentView(mRoot);
		}

		@Override
		public void setListener(Listener listener) {
			mListener = listener;
			
			// When a touch even happens outside of the window
			// make the popup window go away
			mPopupWindow.setTouchInterceptor(new OnTouchListener() {
			public boolean onTouch(View v, MotionEvent event) {
				int hit = hitTestOptions(v, (int)event.getX(), (int)event.getY());
				switch(event.getAction())
				{
					case MotionEvent.ACTION_DOWN:
					case MotionEvent.ACTION_MOVE:
						setSelectedItem(hit);
						break;
							
					case MotionEvent.ACTION_UP:
						if (hit != -1 && mSelectedItem == hit)
						{
							mOptions[hit].mAction.run();
							if (mListener != null)
								mListener.onSelectionClicked(PopupMenuSlider.this, hit);
								
							// Set normal background after click
							View normal = mTrack.getChildAt(hit + 1);
							final DrawableTexture d = DrawableTexture.createDrawableTexture(String.valueOf(LAYOUT_ITEM_BACKGROUND_NORMAL));
							normal.setBackgroundDrawable(d.isLoaded() 
									? d.loadNinePatch(mContext)
									: d.setPadding(
											normal.getPaddingLeft(),
											normal.getPaddingTop(),
											normal.getPaddingRight(),
											normal.getPaddingBottom())
										.loadNinePatch(mContext));
								
							// Reset the selected item
							mSelectedItem = -1;
						}
						break;
							
					case MotionEvent.ACTION_CANCEL:
						setSelectedItem(-1);
						break;
				}
				return true;
			}});
		}
		
		@Override
		public void setOptions(Option[] options) {
			if(mOptions.length == 0) {
				mOptions = options;
				mNeedsLayout = true;
			} else if(mOptions.length > 0 && !Arrays.equals(mOptions, options)) {
				// Remove old items
				// The first and the last item must be stay inside
				mTrack.removeViewsInLayout(1, mTrack.getChildCount() - 2);
				mOptions = options;
				mNeedsLayout = true;
			}
		}
		
		@Override
		public void showAtPoint(View parent, int x, int y, int width, int height) {
			if(mNeedsLayout)
				layout();
			
			int xPos, yPos;
			Rect parentRect = new Rect(x, y, x + width, y + height);
			
			mRoot.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
			mRoot.measure(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
			
			int rootWidth = mRoot.getMeasuredWidth();
			int rootHeight = mRoot.getMeasuredHeight();
			int screenWidth = mWindowManager.getDefaultDisplay().getWidth();
			
			xPos = (screenWidth - rootWidth) / 2;
			yPos = parentRect.top - rootHeight;
			
			boolean onTop = true;
			if(rootHeight > parentRect.top) {
				yPos = parentRect.bottom;
				onTop = false;
			}
			
			showArrow(onTop ? mArrowDown : mArrowUp, onTop ? mArrowUp : mArrowDown, parentRect.centerX());
			setAnimationStyle(mArrowUp.getMeasuredWidth(), screenWidth, parentRect.centerX(), onTop);
			if(mPopupWindow.isShowing()) mPopupWindow.dismiss();
			mPopupWindow.showAtLocation(parent, Gravity.NO_GRAVITY, xPos, yPos);
			mTrack.startAnimation(mTrackAnimation);
		}
		
		@Override
		protected int hitTestOptions(View view, int x, int y) {
			Option[] options = mOptions;
			int numOptions = options.length;
			
			int mHeight = mRoot.getMeasuredHeight();
			if(numOptions != 0 && x >= 0 && y >= 0 && y < mHeight) {
				int padding = LAYOUT_ITEM_PADDING_LEFT + LAYOUT_ITEM_PADDING_RIGHT;
				for (int i = 0; i != numOptions; i++) {
					// The items are located between the first and the last element.
					if(x < mTrack.getChildAt(i + 1).getRight() + padding) {
						return i;
					}
				}
			}
			return -1;
		}

		@Override
		protected void layout() {
			// Mark as not needing layout.
			mNeedsLayout = false;
			
			Option[] options = mOptions;
			int numOptions = options.length;
			for (int i = 0; i != numOptions; ++i) {
				final LinearLayout item = (LinearLayout) mInflater.inflate(R.layout.popup_item, null);
				final ImageView itemIcon = (ImageView) item.findViewById(R.id.icon);
				final TextView itemTitle = (TextView) item.findViewById(R.id.title);
				
				// Set custom item settings
				item.setLayoutParams(LAYOUT_ITEM);
				item.setPadding(LAYOUT_ITEM_PADDING_LEFT, LAYOUT_ITEM_PADDING_TOP, 
						LAYOUT_ITEM_PADDING_RIGHT, LAYOUT_ITEM_PADDING_BOTTOM);
				
				final DrawableTexture d = DrawableTexture.createDrawableTexture(String.valueOf(LAYOUT_ITEM_BACKGROUND_NORMAL));
				item.setBackgroundDrawable(d.isLoaded()
						? d.loadNinePatch(mContext)
						: d.setPadding(
								item.getPaddingLeft(),
								item.getPaddingTop(),
								item.getPaddingRight(),
								item.getPaddingBottom())
							.loadNinePatch(mContext));
				
				item.setOrientation(LAYOUT_ITEM_ORIENTATION);
				item.setGravity(LAYOUT_ITEM_GRAVITY);
				item.setClickable(LAYOUT_ITEM_CLICKABLE);
				item.setFocusable(LAYOUT_ITEM_FOCUSABLE);
				
				// Set custom title settings
				itemTitle.setLayoutParams(LAYOUT_TITLE);
				itemTitle.setGravity(LAYOUT_TITLE_GRAVITY);
				itemTitle.setPadding(LAYOUT_TITLE_PADDING_LEFT, LAYOUT_TITLE_PADDING_TOP,
						LAYOUT_TITLE_PADDING_RIGHT, LAYOUT_TITLE_PADDING_BOTTOM);
				itemTitle.setTextSize(LAYOUT_TITLE_TEXT_SIZE);
				itemTitle.setTextColor(LAYOUT_TITLE_TEXT_COLOR);
				
				final Option option = options[i];
				if(option.mIcon != -1)
					itemIcon.setImageBitmap(ResourceTexture.createResourceTexture(option.mIcon).load(mContext));
				
				if(option.mTitle != null)
					itemTitle.setText(option.mTitle);
				
				mTrack.addView(item, i + 1);
			}
		}

		@Override
		protected void setDrawable(int hit) {
			if(mSelectedItem == -1 && hit >= 0) {
				View pressed = mTrack.getChildAt(hit + 1);
				final DrawableTexture d = DrawableTexture.createDrawableTexture(String.valueOf(LAYOUT_ITEM_BACKGROUND_PRESSED));
				pressed.setBackgroundDrawable(d.isLoaded()
						? d.loadNinePatch(mContext)
						: d.setPadding(
								pressed.getPaddingLeft(),
								pressed.getPaddingTop(),
								pressed.getPaddingRight(),
								pressed.getPaddingBottom())
							.loadNinePatch(mContext));
				
			} else if(mSelectedItem >= 0 && hit >= 0) {
				View normal = mTrack.getChildAt(mSelectedItem + 1);
				final DrawableTexture dNormal = DrawableTexture.createDrawableTexture(String.valueOf(LAYOUT_ITEM_BACKGROUND_NORMAL));
				normal.setBackgroundDrawable(dNormal.isLoaded()
						? dNormal.loadNinePatch(mContext)
						: dNormal.setPadding(
									normal.getPaddingLeft(),
									normal.getPaddingTop(),
									normal.getPaddingRight(),
									normal.getPaddingBottom())
								.loadNinePatch(mContext));
				
				View pressed = mTrack.getChildAt(hit + 1);
				final DrawableTexture dPressed = DrawableTexture.createDrawableTexture(String.valueOf(LAYOUT_ITEM_BACKGROUND_PRESSED));
				pressed.setBackgroundDrawable(dPressed.isLoaded()
						? dPressed.loadNinePatch(mContext)
						: dPressed.setPadding(
								pressed.getPaddingLeft(),
								pressed.getPaddingTop(),
								pressed.getPaddingRight(),
								pressed.getPaddingBottom())
							.loadNinePatch(mContext));
				
			} else if(mSelectedItem >= 0 && hit == -1) {
				View normal = mTrack.getChildAt(mSelectedItem + 1);
				final DrawableTexture d = DrawableTexture.createDrawableTexture(String.valueOf(LAYOUT_ITEM_BACKGROUND_NORMAL));
				normal.setBackgroundDrawable(d.isLoaded()
					? d.loadNinePatch(mContext)
					: d.setPadding(
							normal.getPaddingLeft(),
							normal.getPaddingTop(),
							normal.getPaddingRight(),
							normal.getPaddingBottom())
						.loadNinePatch(mContext));
			}
		}
	}
}
